﻿namespace Centido.Core.Test
{
    using System;
    using System.Data.Common;
    using System.Linq;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    /// <summary>
    /// Testing tables
    /// </summary>
    [TestClass]
    public class TableTest
    {
        /// <summary>
        /// Domain name
        /// </summary>
        public const string DomainName = "FullAdminRights";

        /// <summary>
        /// Domain
        /// </summary>
        private Domain _domain;

        /// <summary>
        /// A table in the domain
        /// </summary>
        private Table _table;

        /// <summary>
        /// Name of that table
        /// </summary>
        private string _tableName;

        /// <summary>
        /// Initializing the test
        /// </summary>
        [TestInitialize]
        public void Initialize()
        {
            _domain = Domain.Build(DomainConfiguration.Load(DomainName));

            _domain.DeleteAllTables();

            _table = _domain.AddTable((_tableName = StringProperty.RandomIdentifier()));

            //Immedialtelly renaming table (for complexity)
            _table.Name = (_tableName = StringProperty.RandomIdentifier());

            _table.GrantAll();
        }

        /// <summary>
        /// Cleaning up after the test
        /// </summary>
        [TestCleanup]
        public void CleanUp()
        {
            _domain.DeleteAllTables();
        }

        /// <summary>
        /// Reading and writing table description
        /// </summary>
        [TestMethod]
        public void ReadingAndWritingTableDescription()
        {
            Assert.IsNotNull(_table);

            //Reading table description
            var description = _table.Description;

            //Table description must be null
            Assert.IsNull(description);

            //New table description
            description = StringProperty.ExtendedValue('A');

            //Writing table description
            _table.Description = description;

            //Table description must be updated
            Assert.AreEqual(_table.Description, description);

            //Writing null description
            _table.Description = null;

            //Table description must be updated
            Assert.AreEqual(_table.Description, null);
        }

        /// <summary>
        /// Writing tool long table description
        /// </summary>
        [TestMethod]
        public void WritingTooLongTableDescription()
        {
            try
            {
                _table.Description = StringProperty.TooLongExtendedValue('A');
            }
            catch (DbException)
            {
                Assert.IsNull(_table.Description);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        /// Checking that table description is trimmed
        /// </summary>
        [TestMethod]
        public void CheckingTableDescriptionTrimming()
        {
            //Not trimmed description
            const string notTrimmedDescription = "\t A\t ";

            _table.Description = notTrimmedDescription;

            //Checking that description was trimmed
            Assert.AreNotEqual(_table.Description, notTrimmedDescription);
            Assert.AreEqual(_table.Description, "A");
        }

        /// <summary>
        /// Reading and writing table display name singular
        /// </summary>
        [TestMethod]
        public void ReadingAndWritingTableDisplayName()
        {
            //Checking that display name is null initially
            Assert.IsNull(_table.DisplayName);

            //New display name singular
            var newDisplayNameSingular = StringProperty.StandardValue('A');

            //Writing to display name singular
            _table.DisplayName = newDisplayNameSingular;

            Assert.AreEqual(_table.DisplayName, newDisplayNameSingular);

            //Writing null to display name singular
            _table.DisplayName = null;

            Assert.IsNull(_table.DisplayName);
        }

        /// <summary>
        /// Writing too long display name singular
        /// </summary>
        [TestMethod]
        public void WritingTooLongDisplayName()
        {
            try
            {
                _table.DisplayName = StringProperty.TooLongStandardValue('A');
            }
            catch (DbException)
            {
                Assert.IsNull(_table.DisplayName);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        /// Checking display name singular trimming
        /// </summary>
        [TestMethod]
        public void CheckingDisplayNameTrimming()
        {
            //Not trimmed display name
            const string notTrimmedDisplayName = "\t A\t ";

            //Writing not trimmed value
            _table.DisplayName = notTrimmedDisplayName;

            //Checking that the value was trimmed
            Assert.AreNotEqual(_table.DisplayName, notTrimmedDisplayName);
            Assert.AreEqual(_table.DisplayName, "A");
        }

        /// <summary>
        /// Table renaming
        /// </summary>
        [TestMethod]
        public void RenamingTable()
        {
            // ReSharper disable UnusedVariable
            var tables = _domain.Tables.ToList(); // Ensuring that the table was stored in the table cache
            // ReSharper restore UnusedVariable

            //Writing some table properties
            const string description = "description";

            _table.Description = description;

            // The old table name
            var oldTableName = _table.Name;

            //New table name
            var newTableName = StringProperty.RandomIdentifier();

            //Changing table name
            _table.Name = newTableName;

            //Checking that table name has changed
            Assert.AreEqual(_table.Name, newTableName.ToLower());

            //Checking that table properties did not change
            Assert.AreEqual(_table.Description, description);

            Assert.IsNull(_domain.GetTable(oldTableName)); // bug fix - checking that the table cache was updated
            Assert.IsNotNull(_domain.GetTable(newTableName));

            //Checking that table can be found in table list
            Assert.IsNotNull(_domain.GetTable(_table.Name));
            Assert.IsTrue(_domain.Tables.Any(t => t.Name == _table.Name));
        }
        


        /// <summary>
        /// Passing a keyword when renaming a table
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void PassingKeywordWhenRenamingTable()
        {
            _table.Name = "SeLeCT";
        }


        /// <summary>
        ///   Renaming table with non-english letters.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void RenamingTableWithNonEnglishLetters()
        {
            _table.Name = "ssdяd";
        }

        /// <summary>
        /// Renaming table that doesn't exist
        /// </summary>
        [TestMethod]
        public void RenamingTableThatDoesntExist()
        {
            //Removing the table
            _table.Delete();

            _table.Refresh();

            Assert.IsNull(_table.Name);

            //Renaming the table
            const string newTableName = "new_name";

            try
            {
                _table.Name = newTableName;
            }
            catch (DbException e)
            {
                Assert.AreEqual(e.GetErrorCode(), DbErrorCode.TableWasNotFound);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        /// Renaming table with duplicate name
        /// </summary>
        [TestMethod]
        public void RenamingTableWithDuplicateName()
        {
            //Creating another table
            var table = _domain.AddTable("another_table");

            try
            {
                table.Name = _tableName;
            }
            catch (DbException e)
            {
                Assert.AreEqual(e.GetErrorCode(), DbErrorCode.AddingTableWithDuplicateName);
                return;
            }

            Assert.Fail();
        }
        

        /// <summary>
        /// Writing null to table name
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void WritingNullToTableName()
        {
            _table.Name = null;
        }

        /// <summary>
        /// Writing empty string to table name
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void WritingEmptyStringToTableName()
        {
            _table.Name = "\t ";
        }

        /// <summary>
        /// Writing too long string to table name
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void WritingTooLongStringToTableName()
        {
            _table.Name = StringProperty.TooLongIdentifier('a');
        }

        /// <summary>
        /// Checking table name trimming and lowering
        /// </summary>
        [TestMethod]
        public void CheckingTableNameTrimmingAndLowering()
        {
            _table.Name = "\t A\t ";

            Assert.AreEqual(_table.Name, "a");
        }

        /// <summary>
        /// Reading properties from the table that doesn't exist
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(DbOperationException))]
        public void ReadingPropertiesFromTableThatDoesntExist()
        {
            //Setting some properties
            _table.Description = "description";

            _table.Delete();

            _table.Refresh();

            //Reading those properties
            Assert.IsNull(_table.Description);

            //Writing those properties
            _table.Description = "description";
        }

        /// <summary>
        /// Checking row count
        /// </summary>
        [TestMethod]
        public void CheckingRowCount()
        {
            //There must be zero rows in the table
            Assert.AreEqual(_table.RowCount, 0);

            //Inserting new row
            _table.InsertRow();

            //There must be one rows in the table
            Assert.AreEqual(_table.RowCount, 1);

            //Deleting all rows
            _table.DeleteAllRows();

            //There must be zero rows in the table
            Assert.AreEqual(_table.RowCount, 0);
        }

        /// <summary>
        /// CHecking references between tables
        /// </summary>
        [TestMethod]
        public void CheckingReferencesBetweenTables()
        {
            //Initially there must be no references
            Assert.AreEqual(_table.RefsTo.Count(), 0);
            Assert.AreEqual(_table.RefsFrom.Count(), 0);

            //Creating another table
            var anotherTable = _domain.AddTable("another_table");

            //Creating reference from one to another
            var firstReference = _table.AddColumn("ref", ColumnType.Reference) as RefColumn;

            if (firstReference == null) return;

            firstReference.AddTarget(anotherTable);

            Assert.AreEqual(_table.RefsTo.Count(), 0);
            Assert.AreEqual(_table.RefsFrom.Count(), 1);

            Assert.AreEqual(_table.RefsFrom.First().Column.Table.Name, _table.Name);
            Assert.AreEqual(_table.RefsFrom.First().RefTable.Name, anotherTable.Name);

            Assert.AreEqual(anotherTable.RefsTo.Count(), 1);
            Assert.AreEqual(anotherTable.RefsFrom.Count(), 0);

            Assert.AreEqual(anotherTable.RefsTo.First().Column.Table.Name, _table.Name);
            Assert.AreEqual(anotherTable.RefsTo.First().RefTable.Name, anotherTable.Name);

            //Deleting that reference
            firstReference.Delete();

            Assert.AreEqual(_table.RefsTo.Count(), 0);
            Assert.AreEqual(_table.RefsFrom.Count(), 0);
            Assert.AreEqual(anotherTable.RefsTo.Count(), 0);
            Assert.AreEqual(anotherTable.RefsFrom.Count(), 0);
        }


        /// <summary>
        ///   Getting a column by id
        /// </summary>
        [TestMethod]
        public void GettingColumnById()
        {
            Assert.IsNull(_table.GetColumn(StringProperty.RandomIdentifier()));

            //Creating a column
            var initialColumn = _table.AddColumn(StringProperty.RandomIdentifier(), ColumnType.Integer);

            //Getting the column by name
            var column = _table.GetColumn(initialColumn.Id);

            Assert.IsNotNull(column);
            Assert.AreEqual(column.Id, initialColumn.Id);
            Assert.AreEqual(column.Name, initialColumn.Name);
        }

        /// <summary>
        ///   Getting a column by name
        /// </summary>
        [TestMethod]
        public void GettingColumnByName()
        {
            Assert.IsNull(_table.GetColumn(StringProperty.RandomIdentifier()));

            //Creating a column
            var columnName = StringProperty.RandomIdentifier();

            var initialColumn = _table.AddColumn(columnName, ColumnType.Integer);

            //Getting the column by name
            var column = _table.GetColumn(columnName);

            Assert.IsNotNull(column);
            Assert.AreEqual(column.Id, initialColumn.Id);
            Assert.AreEqual(column.Name, initialColumn.Name);
        }


        /// <summary>
        ///   Passing null when getting column by name
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void PassingNullWhenGettingColumnByName()
        {
            _table.GetColumn(null);
        }


        /// <summary>
        ///   Passing empty string when getting column by name
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void PassingEmptyStringWhenGettingColumnByName()
        {
            _table.GetColumn("   ");
        }
    }
}